<template>
  <div class="validate-input">
    <label class="input-label" v-if="labeltext">{{ labeltext }}</label>
    <div class="input-content clear">
      <input
        v-if="tagType !== 'textarea'"
        :class="[{ 'is-invalid': inputRef.error }, 'control']"
        @blur="validateInput"
        :value="inputRef.val"
        @input="updateValue"
        v-bind="$attrs"
        :maxlength="maxLength"
        @change="onInputChange"
      />
      <textarea
        v-else
        :class="[{ 'is-invalid': inputRef.error }, 'control']"
        @blur="validateInput"
        :value="inputRef.val"
        @input="updateValue"
        v-bind="$attrs"
        :maxlength="maxLength"
        @change="onInputChange"
      ></textarea>
      <span
        v-if="maxLength"
        :class="[
          'input-count',
          { 'input-count-light': inputRef.val.length >= maxLength },
        ]"
        >{{
          inputRef.val.length > maxLength ? maxLength : inputRef.val.length
        }}
        / {{ maxLength }}</span
      >
      <span class="input-error" v-if="inputRef.error">{{
        inputRef.message
      }}</span>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, PropType, reactive, onMounted, watch } from "vue";
import { emitter } from "@/components/ValidateForm.vue";
interface RuleProps {
  type: "required" | "email" | "custom";
  message: string;
  validator?: (val: string) => boolean;
}
const emialReg = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/;
export type RulesProps = RuleProps[];
type TagType = "input" | "textarea";
export default defineComponent({
  name: "ValidateInput",
  emits: ["inputChange", "update:modelValue"],
  props: {
    /* 验证规则 */
    rules: {
      type: Array as PropType<RulesProps>,
    },
    /* input类型 */
    tagType: {
      type: String as PropType<TagType>,
      default: "input",
    },
    // label值
    labeltext: String,
    // 组件自定义v-model的默认prop
    modelValue: String,
    maxLength: Number,
  },
  // 从父组件中添加的非props属性添加不添加到组件根元素上
  inheritAttrs: false,
  setup(props, context) {
    /* 组件注册时初始化值一次 */
    const inputRef = reactive({
      val: props.modelValue || "",
      message: "",
      error: false,
    });
    /* 根据props的rules验证input的值 */
    const validateInput = () => {
      if (props.rules) {
        const allPass = props.rules.every((rule) => {
          // js是静态作用域
          inputRef.message = rule.message;
          let pass = true;
          switch (rule.type) {
            case "required":
              pass = inputRef.val.trim() !== "";
              break;
            case "email":
              pass = emialReg.test(inputRef.val);
              break;
            case "custom":
              pass = rule.validator ? rule.validator(inputRef.val) : true;
              break;
            default:
              break;
          }
          return pass;
        });
        // 表单项验证错误验证
        inputRef.error = !allPass;
        return allPass;
      } else return true;
    };
    /* 当props.modelValue改变时，更改inputRef.val的值 */
    watch(
      () => props.modelValue,
      (now, prev) => (inputRef.val = now || "")
    );
    // 双向数据绑定，绑定事件 --- 更新数据
    const updateValue = (e: Event) => {
      const targetValue = (e.target as HTMLInputElement).value;
      inputRef.val = targetValue;
      // 组件中传入动态值 v-model 表单组件 v-model默认porp和事件名
      context.emit("update:modelValue", targetValue);
    };
    function onInputChange(e: Event) {
      context.emit("inputChange", e);
    }
    // 在app.vue中使用一个validateInput组件，就onMounted时期触发一次'form-item-created'事件，将validateInput添加进funcArr
    onMounted(() => {
      // 创建时向validateform触发form-item-created,
      // js是静态作用域，传递validateInput过去执行，函数内部可以访问到此处inputRef和rules
      emitter.emit("form-item-created", validateInput);
    });

    return {
      inputRef,
      validateInput,
      updateValue,
      onInputChange,
    };
  },
});
</script>
<style lang="scss" scoped>
.validate-input {
  display: flex;
  align-items: center;
  padding-bottom: 16px;
}
.input-label {
  text-align: right;
  font-size: 16px;
  color: #606266;
  padding: 0 12px 0 0;
  box-sizing: border-box;
  min-width: 80px;
}
.input-content {
  flex: 1;
}
.control {
  line-height: 24px;
  display: inline-block;
  vertical-align: middle;
  box-sizing: border-box;
  width: 100%;
  padding: 0.5rem 0.75rem;
  font-size: 1rem;
  font-weight: 400;
  color: #37393c;
  background-color: #fff;
  background-clip: padding-box;
  border: 2px solid #ced4da;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border-radius: 0.25rem;
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.control:focus-visible {
  outline: #60a5fa auto 0.8px;
}
.input-count {
  color: #909399;
  font-size: 12px;
  line-height: 14px;
  padding: 4px 4px 0 0;
  float: right;
}
.input-count-light {
  color: #2b82eb;
}
.input-error {
  color: #f56c6c;
  font-size: 14px;
  line-height: 0px;
  padding-top: 4px;
}
</style>
